<?php

/*
 * Copyright (C) 2009 - 2011 Pham Cong Dinh
 *
 * This file is part of Spica.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

// namespace spica\core\utils;

/**
 * The SpicaArrayUtils provides many useful array methods, in addition to
 * built-in php array functions.
 *
 * namespace spica\core\utils\ArrayUtils
 *
 * @category   spica
 * @package    core
 * @subpackage utils
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.2
 * @since      March 23, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: ArrayUtils.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaArrayUtils
{
    /**
     * Prevents object instantiation.
     */
    private function __construct() {}

    /**
     * Returns a random element for an array.
     *
     * @param  array $array array from which to return random element.
     * @param  bool  $ifEmpty value to return if $array is empty.
     * @return mixed a random element from $array, or $default if $array is empty
     */
    public static function random($array, $ifEmpty = null)
    {
        return count($array) ? $array[array_rand($array)] : $ifEmpty;
    }

    /**
     * Sorts an array of arrays by comparing values found at a specific index.
     *
     * @param  array $array array to sort
     * @param  string|int $key key from which to retrieve sort value
     * @param  SpicaArraySortable $helper Helper object which supports array sorting
     * @return true on success, false on failure
     */
    public static function kvsort(&$array, $key = null, $helper = null)
    {
        // if the array is empty.
        if (true === empty($array))
        {
            return;
        }

        // If no key to sort by is specified, use the first key of the first element.
        if (null === $key)
        {
            reset($array);
            $key = array_shift(array_keys(current($array)));
        }

        if (null === $helper)
        {
            $helper = new SpicaArraySortHelper();
        }

        if ($helper instanceof SpicaArraySortable)
        {
            $helper->setKey($key);
            return usort($array, array($helper, 'compare'));
        }

        throw new InvalidArgumentException('The helper object for sorting array must be derived from SpicaArraySortable.');
    }

    /**
     * Sorts an array of arrays in reverse order by comparing values found at a
     * specific index.
     *
     * @param  array $array array to sort in reverse order
     * @param  mixed $key key from which to retrieve sort value
     * @param  SpicaArraySortable $helper Helper object which supports array sorting
     * @return true on success, false on failure
     */
    public static function kvrsort(&$array, $key = null, $helper = null)
    {
        // if the array is empty.
        if (true === empty($array))
        {
            return;
        }

        // If no key to sort by is specified, use the first key of the first element.
        if (null === $key)
        {
            reset($array);
            $key = array_shift(array_keys(current($array)));
        }

        if (null === $helper)
        {
            $helper = new SpicaArraySortHelper();
        }

        if ($helper instanceof SpicaArraySortable)
        {
            $helper->setKey($key);
            return usort($array, array($helper, 'reverseCompare'));
        }

        throw new InvalidArgumentException('The helper object for sorting array must be derived from SpicaArraySortable.');
    }

    /**
     * Returns only those elements of an array that match the specified pattern.
     *
     * @param  array  $array   An array to search for matches.
     * @param  string $pattern A regular expression to search for.
     * @param  bool   $returnMatches If true the matching elements of the array
     *         get returned, the not matching elements otherwise.
     * @return array  An array with the matching/not matching elements.
     */
    public static function grep($array, $pattern, $returnMatches = true)
    {
        // Must be replaced with lambda function in PHP 5.3 (when it is available)
        // FIXME
        $grepped = array_filter($array, create_function('$a', 'return preg_match("/' . str_replace('"', '\"', $pattern) . '/", $a);'));

        if ($returnMatches)
        {
            return $grepped;
        }

        return array_values(array_diff($grepped, $array));
    }

	/**
	 * Returns reference to array item or $default if item is not set.
     * 
	 * @param  mixed  array
	 * @param  mixed  key
	 * @return mixed
	 */
	public static function & getRef(& $arr, $key)
	{
		foreach (is_array($key) ? $key : array($key) as $k)
        {
			if (is_array($arr) || null === $arr)
            {
				$arr = & $arr[$k];
			} 
            else
            {
				throw new InvalidArgumentException('Traversed item is not an array.');
			}
		}

		return $arr;
	}

	/**
	 * Recursively appends elements of remaining keys from the second array to the first.
     *
	 * @param  array
	 * @param  array
	 * @return array
	 */
	public static function mergeTree($arr1, $arr2)
	{
		$res = $arr1 + $arr2;
		foreach (array_intersect_key($arr1, $arr2) as $k => $v)
        {
			if (is_array($v) && is_array($arr2[$k]))
            {
				$res[$k] = self::mergeTree($v, $arr2[$k]);
			}
		}
        
		return $res;
	}


	/**
	 * Searches the array for a given key and returns the offset if successful.
	 * @param  array  input array
	 * @param  mixed  key
	 * @return int    offset if it is found, FALSE otherwise
	 */
	public static function getKeyOffset($arr, $key)
	{
		$foo = array($key => null);
		return array_search(key($foo), array_keys($arr), true);
	}

	/**
	 * Renames key in array.
	 * @param  array
	 * @param  mixed  old key
	 * @param  mixed  new key
	 * @return void
	 */
	public static function renameKey(array &$arr, $oldKey, $newKey)
	{
		$offset = self::getKeyOffset($arr, $oldKey);

		if ($offset !== false)
        {
			$keys = array_keys($arr);
			$keys[$offset] = $newKey;
			$arr = array_combine($keys, $arr);
		}
	}

    /**
	 * Inserts new array before item specified by key.
	 * @param  array  input array
	 * @param  mixed  key
	 * @param  array  inserted array
	 * @return void
	 */
	public static function insertBefore(array &$arr, $key, array $inserted)
	{
		$offset = self::getKeyOffset($arr, $key);
		$arr = array_slice($arr, 0, $offset, true) + $inserted + array_slice($arr, $offset, count($arr), true);
	}

	/**
	 * Inserts new array after item specified by key.
	 * @param  array  input array
	 * @param  mixed  key
	 * @param  array  inserted array
	 * @return void
	 */
	public static function insertAfter(array &$arr, $key, array $inserted)
	{
        $offset = self::getKeyOffset($arr, $key);
		$offset = $offset === false ? count($arr) : $offset + 1;
		$arr = array_slice($arr, 0, $offset, true) + $inserted + array_slice($arr, $offset, count($arr), true);
	}
}

/**
 * Interface for helpers that allow sorting arrays on arbitrary criteria for usort/uasort.
 *
 * @category   spica
 * @package    core
 * @subpackage utils
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.2
 * @since      March 23, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: ArrayUtils.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
interface SpicaArraySortable
{
    /**
     * Sets key to sort.
     *
     * @param scalar $key
     */
    public function setKey($key);

    /**
     * Compares two associative arrays by the array key defined in self::$key,
     * using in uasort().
     *
     * @param array $left
     * @param array $right
     */
    public function compare($left, $right);

    /**
     * Compare, in reverse order, two associative arrays by the array
     * key defined in self::$key, using in uasort().
     *
     * @param array $left
     * @param array $right
     */
    public function reverseCompare($left, $right);

    /**
     * Compare array keys case insensitively for uksort.
     *
     * @param scalar $left
     * @param scalar $right
     */
    public function compareKeys($left, $right);

    /**
     * Compare, in reverse order, array keys case insensitively for uksort.
     *
     * @param scalar $left
     * @param scalar $right
     */
    public function reverseCompareKeys($left, $right);
}

/**
 * Spica helper implementation that allow sorting arrays on arbitrary criteria for usort/uasort.
 *
 * @category   spica
 * @package    core
 * @subpackage utils
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.2
 * @since      March 23, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: ArrayUtils.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaArraySortHelper implements SpicaArraySortable
{
    /**
     * The array key to sort by.
     *
     * @var string
     */
    public $key;

    /**
     * Sets key to sort.
     *
     * @param scalar $key
     */
    public function setKey($key)
    {
        $this->key = $key;
    }

    /**
     * Compares two associative arrays by the array key defined in self::$key.
     *
     * @param  array $left
     * @param  array $right
     * @return bool true if the value at the given key of the left array is greater, false otherwise
     */
    public function compare($left, $right)
    {
        return strcoll(strtolower($left[$this->key]), strtolower($right[$this->key]));
    }

    /**
     * Compares, in reverse order, two associative arrays by the array
     * key defined in self::$key.
     *
     * @param  array $left
     * @param  array $right
     * @return bool true if the value at the given key of the left array is smaller, false otherwise
     */
    public function reverseCompare($left, $right)
    {
        return strcoll(strtolower($right[$this->key]), strtolower($left[$this->key]));
    }

    /**
     * Compares array keys case insensitively for uksort().
     *
     * @param  scalar $left
     * @param  scalar $right
     * @return bool true if the first key is greater order than the key on the right
     */
    public function compareKeys($left, $right)
    {
        return strcoll(strtolower($left), strtolower($right));
    }

    /**
     * Compares, in reverse order, array keys case insensitively for uksort().
     *
     * @param  scalar $left
     * @param  scalar $right
     * @return bool true if the first key is smaller order than the key on the right
     */
    public function reverseCompareKeys($a, $b)
    {
        return strcoll(strtolower($b), strtolower($a));
    }
}

?>